package com.nbjtjc.safe.oss.service;

import cn.hutool.core.codec.Base64Encoder;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.model.MatchMode;
import com.aliyun.oss.model.PolicyConditions;
import com.nbjtjc.safe.Exception.SafeException;
import com.nbjtjc.safe.model.sys.SysFile;
import com.nbjtjc.safe.service.sys.SysFileService;
import com.nbjtjc.safe.util.MyShiroUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URI;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @auther guqianbin
 * @date 2018/11/21 10:26
 **/
public class AliyunCloudStorageService extends CloudStorageService {
    private OSSClient client;
    private String endPoint;
    private String accessKeyId;
    private String accessSeySecret;
    private String bucketName;
    private String domain;
    private String prefix;
    private String callbackUrl;

    private SysFileService sysFileService;
    public AliyunCloudStorageService() {

    }

    public AliyunCloudStorageService(String endPoint, String accessKeyId
            , String accessSeySecret, String bucketName, String domain
            , String prefix, String callbackUrl,SysFileService sysFileService) {
        this.endPoint = endPoint;
        this.accessKeyId = accessKeyId;
        this.accessSeySecret = accessSeySecret;
        this.bucketName = bucketName;
        this.domain = domain;
        this.prefix = prefix;
        this.callbackUrl = callbackUrl;
        this.sysFileService=sysFileService;
        this.init();
    }

    public OSSClient getClient() {
        return client;
    }


    private void init() {
        client = new OSSClient(endPoint, accessKeyId,
                accessSeySecret);
    }

    public void delete(String objectName){
        try {
            client.deleteObject(bucketName,objectName);
        } catch (OSSException e) {
            e.printStackTrace();
        } catch (ClientException e) {
            e.printStackTrace();
        }finally {
            client.shutdown();
        }
    }

    @Override
    public String upload(byte[] data, String path) {
        return upload(new ByteArrayInputStream(data), path);
    }

    @Override
    public String upload(InputStream inputStream, String path) {
        try {
            client.putObject(bucketName, path, inputStream);
        } catch (Exception e) {
            throw new SafeException("上传文件失败，请检查配置信息", e);
        }finally {
            client.shutdown();
        }

        return domain + "/" + path;
    }

    @Override
    public String uploadSuffix(byte[] data, String suffix) {
        return upload(data, getPath(prefix, suffix));
    }

    @Override
    public String uploadSuffix(InputStream inputStream, String suffix) {
        return upload(inputStream, getPath(prefix, suffix));
    }

    /**
     * 签名，将参数名字，回调函数，回调函数的参数，相关参数编码返回给前端
     *
     * @return
     */
    public String sign(HttpServletRequest request, HttpServletResponse response) {
        Integer contentId = Integer.valueOf(request.getParameter("contentId"));
        Integer contentType = Integer.valueOf(request.getParameter("contentType"));
        String fileKey=String.valueOf(request.getParameter("fileKey"));
        String realName=request.getParameter("realName");//获取文件真实名字
        try {
            long expireTime = 30;
            long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
            Date expiration = new Date(expireEndTime);
            PolicyConditions policyConds = new PolicyConditions();
            policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
            policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, prefix);

            String postPolicy = client.generatePostPolicy(expiration, policyConds);
            byte[] binaryData = postPolicy.getBytes("utf-8");
            String encodedPolicy = BinaryUtil.toBase64String(binaryData);
            String postSignature = client.calculatePostSignature(postPolicy);

            Map<String, String> respMap = new LinkedHashMap<String, String>();
            respMap.put("accessid", accessKeyId);
            respMap.put("policy", encodedPolicy);
            respMap.put("signature", postSignature);
            respMap.put("dir", getPath(prefix));
            respMap.put("host", domain);
            respMap.put("expire", String.valueOf(expireEndTime / 1000));
            // respMap.put("expire", formatISO8601Date(expiration));

            JSONObject jasonCallback = new JSONObject();
            jasonCallback.put("callbackUrl", this.callbackUrl);
            jasonCallback.put("callbackBody",
                    "filename=${object}&size=${size}" +
                            "&mimeType=${mimeType}" +
                            "&height=${imageInfo.height}" +
                            "&width=${imageInfo.width}" +
                            "&contentId=" + contentId +
                            "&contentType=" + contentType+
                            "&fileKey="+fileKey+
                            "&realName="+realName+
                            "&userId="+ MyShiroUtils.getSessionUserId());
            jasonCallback.put("callbackBodyType", "application/x-www-form-urlencoded");
            String base64CallbackBody = BinaryUtil.toBase64String(jasonCallback.toString().getBytes());
            respMap.put("callback", base64CallbackBody);

            JSONObject ja1 = JSONObject.parseObject(JSON.toJSONString(respMap));
            System.out.println(ja1.toString());
            return ja1.toJSONString();

        } catch (Exception e) {
            // Assert.fail(e.getMessage());
            System.out.println(e.getMessage());
            return "";
        }
    }

    /**
     * 阿里云调用系统的回调函数要执行的内容
     * @param response
     * @param request
     * @throws IOException
     */
    public void callback(HttpServletResponse response, HttpServletRequest request) throws IOException {

        String ossCallbackBody = GetPostBody(request.getInputStream(),
                Integer.parseInt(request.getHeader("content-length")));
        boolean ret = VerifyOSSCallbackRequest(request, ossCallbackBody);
        System.out.println("verify result : " + ret);
        SysFile sysFile=insertFile(request);
        Map<String,String> resultMap=new HashMap<>();
        JSONObject jsonResult = JSONObject.parseObject(JSON.toJSONString(sysFile));
        resultMap.put("file",BinaryUtil.toBase64String(jsonResult.toJSONString().getBytes()));
        // System.out.println("OSS Callback Body:" + ossCallbackBody);
        //TODO 暂时设定为签名验证OK
        if (ret) {

            resultMap.put("Status","OK");
//            response(request, response,jsonResult.toJSONString(), HttpServletResponse.SC_OK);
            response(request, response,JSON.toJSONString(resultMap), HttpServletResponse.SC_OK);
        } else {
            resultMap.put("Status","OK");
//            response(request, response,  jsonResult.toJSONString(), HttpServletResponse.SC_OK);
            response(request, response,JSON.toJSONString(resultMap), HttpServletResponse.SC_OK);
//            response(request, response, "{\"Status\":\"verdify not ok\"}", HttpServletResponse.SC_BAD_REQUEST);
        }
    }

    private SysFile insertFile(HttpServletRequest request){
        Integer contentId= Integer.valueOf(request.getParameter("contentId"));
        Integer contentType=Integer.valueOf(request.getParameter("contentType"));
        Integer userId=Integer.valueOf(request.getParameter("userId"));
        String fileName=request.getParameter("filename");//文件在aliyun对象存储上的存放路径
        String realName=request.getParameter("realName");//文件的真实名字
        SysFile sysFile=new SysFile();
        sysFile.setPath(fileName);
        sysFile.setContentType(contentType);
        sysFile.setContentId(contentId);
        sysFile.setFileName(realName);
        sysFile.setUploadTime(new Date());
        sysFile.setUserId(userId);
        if(StringUtils.isNotEmpty(request.getParameter("fileKey"))){
            sysFile.setFileKey(request.getParameter("fileKey"));
        }
        sysFileService.add(sysFile);
        return sysFile;
    }
    /**
     * 服务器响应结果
     *
     * @param request
     * @param response
     * @param results
     * @param status
     * @throws IOException
     */
    private void response(HttpServletRequest request, HttpServletResponse response, String results, int status)
            throws IOException {
        String callbackFunName = request.getParameter("callback");
        response.addHeader("Content-Length", String.valueOf(results.length()));
        if (callbackFunName == null || callbackFunName.equalsIgnoreCase(""))
            response.getWriter().println(results);
        else
            response.getWriter().println(callbackFunName + "( " + results + " )");
        response.setStatus(status);
        response.flushBuffer();
    }

    /**
     * 服务器响应结果
     */
    private void response(HttpServletRequest request, HttpServletResponse response, String results) throws IOException {
        String callbackFunName = request.getParameter("callback");
        if (callbackFunName == null || callbackFunName.equalsIgnoreCase(""))
            response.getWriter().println(results);
        else
            response.getWriter().println(callbackFunName + "( " + results + " )");
        response.setStatus(HttpServletResponse.SC_OK);
        response.flushBuffer();
    }

    /**
     * 获取Post消息体
     *
     * @param is
     * @param contentLen
     * @return
     */
    public String GetPostBody(InputStream is, int contentLen) {
        if (contentLen > 0) {
            int readLen = 0;
            int readLengthThisTime = 0;
            byte[] message = new byte[contentLen];
            try {
                while (readLen != contentLen) {
                    readLengthThisTime = is.read(message, readLen, contentLen - readLen);
                    if (readLengthThisTime == -1) {// Should not happen.
                        break;
                    }
                    readLen += readLengthThisTime;
                }
                return new String(message);
            } catch (IOException e) {
            }
        }
        return "";
    }

    /**
     * 验证上传回调的Request
     *
     * @param request
     * @param ossCallbackBody
     * @return
     * @throws NumberFormatException
     * @throws IOException
     */
    protected boolean VerifyOSSCallbackRequest(HttpServletRequest request, String ossCallbackBody)
            throws NumberFormatException, IOException {
        boolean ret = false;
        String autorizationInput = new String(request.getHeader("Authorization"));
        String pubKeyInput = request.getHeader("x-oss-pub-key-url");
        byte[] authorization = BinaryUtil.fromBase64String(autorizationInput);
        byte[] pubKey = BinaryUtil.fromBase64String(pubKeyInput);
        String pubKeyAddr = new String(pubKey);
        if (!pubKeyAddr.startsWith("http://gosspublic.alicdn.com/")
                && !pubKeyAddr.startsWith("https://gosspublic.alicdn.com/")) {
            System.out.println("pub key addr must be oss addrss");
            return false;
        }
        String retString = executeGet(pubKeyAddr);
        retString = retString.replace("-----BEGIN PUBLIC KEY-----", "");
        retString = retString.replace("-----END PUBLIC KEY-----", "");
        String queryString = request.getQueryString();
        String uri = request.getRequestURI();
        String decodeUri = java.net.URLDecoder.decode(uri, "UTF-8");
        String authStr = decodeUri;
        if (queryString != null && !queryString.equals("")) {
            authStr += "?" + queryString;
        }
        authStr += "\n" + ossCallbackBody;
        ret = doCheck(authStr, authorization, retString);
        return ret;
    }

    /**
     * 获取public key
     *
     * @param url
     * @return
     */
    @SuppressWarnings({"finally"})
    public String executeGet(String url) {
        BufferedReader in = null;

        String content = null;
        try {
            // 定义HttpClient
            @SuppressWarnings("resource")
            DefaultHttpClient client = new DefaultHttpClient();
            // 实例化HTTP方法
            HttpGet request = new HttpGet();
            request.setURI(new URI(url));
            HttpResponse response = client.execute(request);

            in = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
            StringBuffer sb = new StringBuffer("");
            String line = "";
            String NL = System.getProperty("line.separator");
            while ((line = in.readLine()) != null) {
                sb.append(line + NL);
            }
            in.close();
            content = sb.toString();
        } catch (Exception e) {
        } finally {
            if (in != null) {
                try {
                    in.close();// 最后要关闭BufferedReader
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            return content;
        }
    }

    /**
     * 验证RSA
     *
     * @param content
     * @param sign
     * @param publicKey
     * @return
     */
    public static boolean doCheck(String content, byte[] sign, String publicKey) {
        try {
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            byte[] encodedKey = BinaryUtil.fromBase64String(publicKey);
            PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
            java.security.Signature signature = java.security.Signature.getInstance("MD5withRSA");
            signature.initVerify(pubKey);
            signature.update(content.getBytes());
            boolean bverify = signature.verify(sign);
            return bverify;

        } catch (Exception e) {
            e.printStackTrace();
        }

        return false;
    }
}
